package com.nju.crawler.service;

import antlr.StringUtils;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.nju.crawler.domain.ResultResp;
import com.nju.crawler.executor.AbstractCrawlGithubATask;
import com.nju.crawler.executor.CCLabThreadManager;
import com.nju.crawler.executor.CrawlGithubTask;
import com.nju.crawler.executor.Task;
import com.nju.crawler.repository.PersistService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @authorliyunfei
 * @date2022/11/13
 **/
@Service
public class CrawlService {

    @Value("${user.home}")
    private String defaultPath;
    // 维护爬取的index指针
    private volatile AtomicInteger atomicInteger = new AtomicInteger(0);

    private static final int size = 10;//设计静态类或class对比
    private static final int currencyNum = 5;


    // 单次爬取(递归)
    @Autowired
    PersistService persistService;
    public ResultResp<String> singleCrawl(int startIndex,String savePath){
        ResultResp<String> resultResp = new ResultResp<>(200,"success");
        CrawlGithubTask task = new CrawlGithubTask("crawl-task",startIndex,savePath);//atomicInteger.get()
        task.setPersistService(persistService);
        CCLabThreadManager.executeService(task);
        return resultResp;
    }

    // 定时爬取


    /**
     * 爬取用户信息 & 用户仓库关联信息
     */
    public ResultResp<String> crawlUserInfo(){
        ResultResp<String> resultResp = new ResultResp<>(200,"success");

        //for(int i=0;i<5;i++)
              //CCLabThreadManager.executeService(new CrawlUserInfoTask(i *1000,(i+1)*1000));
        CCLabThreadManager.executeService(new CrawlUserInfoTask(0,10000));

        return resultResp;
    }

    public ResultResp<String> crawlUserRepoInfo(){
        ResultResp<String> resultResp = new ResultResp<>(200,"success");

        //for(int i=0;i<5;i++)
        //CCLabThreadManager.executeService(new CrawlUserInfoTask(i *1000,(i+1)*1000));
        CCLabThreadManager.executeService(new CrawlUserRepoTask());

        return resultResp;
    }


    void currencyExecute(int nThread){
         //TODO ，测试评估（网络请求时间、） 一次任务执行多久写库合适，， DB处理一批数据多长合适
         // 在Dao层去控制并发，对比
          SaveToDbResource saveToDbResource = new SaveToDbResource();
          for(int i=1;i<=currencyNum;i++){
              CCLabThreadManager.executeService(new CrawlUserInfoTask(i,0,20,saveToDbResource));
              // 按偏移量修改新的startIndex    size * concurreny
          }

    }

    private class SaveToDbResource{
           //
           final ConcurrentHashMap<Integer,List<Object[]>> resources = new ConcurrentHashMap<>(currencyNum);

           volatile boolean[] permits = new boolean[currencyNum];// 其他线程修改，需要保证可见性，不存在同时并发修改

          public SaveToDbResource() {
                Arrays.fill(permits,true);
          }
          // 可并发访问（读操作）数组--并行
          boolean getPermit(Integer index){return permits[index];}

          //volatile boolean canWriteToDb = false;
           synchronized void put(Integer plot,List<Object[]> list){
                 resources.put(plot,list);
                 permits[plot-1] = false;
                 if(resources.size()==currencyNum){
                     // 五个都收集齐了则可以写库--观察者模式
                     //canWriteToDb = true;
                     // 职责越界了有点
                     System.out.println("ready to write to db"+resources.size());
                     persistService.batchInsertUserByArgs(getSeqListAndClear());
                     System.out.println("finish to write to db"+resources.size());
                 }
           }

           // 内部类，private都可以访问
           private List<Object[]> getSeqListAndClear(){
                 //List<Object[]> list = new LinkedList<>();
                 List<Object[]> list = new ArrayList<>(currencyNum);
                 for(int i = 1; i<=currencyNum; i++){
                     list.addAll(resources.get(i));
                     permits[i-1] = true;
                 }
                 resources.clear();
                 return list;
           }
           private void clear(){
                resources.clear();
           }
    }

    // 请求 并 遍历 用户数据
    // (1) 请求followers，拼接IDs, 用户list（followers，插入数据库（唯一主键，保证用户的唯一性）
    // (2) 请求repos, 插入star,own关系表以及用户表

    // 通过单个用户递归，直到该用户无任何联系在回退---DFS
    private class CrawlUserInfoTask extends AbstractCrawlGithubATask{
        private int id;
        private int starIndex;
        private int endIndex;
        //int maxId = Integer.MAX_VALUE;// 记录 最大的ID
        private SaveToDbResource saveToDbResource;

        public CrawlUserInfoTask(int id, int starIndex, int endIndex, SaveToDbResource saveToDbResource) {
            this.id = id;
            this.starIndex = starIndex;
            this.endIndex = endIndex;
            this.saveToDbResource = saveToDbResource;
        }

        public CrawlUserInfoTask(int id, int starIndex, int endIndex) {
            this.id = id;
            this.starIndex = starIndex;
            this.endIndex = endIndex;
        }

        public CrawlUserInfoTask(int starIndex, int endIndex) {
            this.starIndex = starIndex;
            this.endIndex = endIndex;
        }

        public void setSaveToDbResource(SaveToDbResource saveToDbResource) {
            this.saveToDbResource = saveToDbResource;
        }

        @Override
        public void run() {
              System.out.println(Thread.currentThread().getName()+" start crawl");
               while (!isStop && starIndex <endIndex){
                   // 阻塞--
                   while (!saveToDbResource.getPermit(id-1));

                   try{
                       JSONArray rs = requestUserInfo(starIndex);

                       Iterator<Object> iterator=  rs.iterator();
                       List<Object[]> list = new LinkedList<>();

                       while (iterator.hasNext()){

                             JSONObject jsonObject= new JSONObject(iterator.next());

                             final String userName = jsonObject.getStr("login");
                             final Integer userId = jsonObject.getInt("id");
                             starIndex = userId;// 更新下次--
                             final String followUrl = jsonObject.getStr("followers_url");
                             System.out.println("id:"+userId);
                             // 处理用户表 → 用户关系图构建
                             String followRes = request(followUrl);
                             JSONArray followArr = new JSONArray(followRes);

                             Iterator<Object> followIterator=  followArr.iterator();
                             StringBuilder stringBuilder = new StringBuilder();

                             while (followIterator.hasNext()){
                                 JSONObject followJsonObject= new JSONObject(followIterator.next());
                                 stringBuilder.append(followJsonObject.getInt("id"));
                                 stringBuilder.append(",");
                                 // 简单处理，若采用递归，一直爬取--，不太好处理，，后续考虑
                             }

                             String followIds;
                             if(stringBuilder.length()<=2){
                                 followIds = "";
                             }else{
                                 followIds = stringBuilder.substring(0,stringBuilder.length()-2);
                             }
                             Object[] userObject = new Object[4];
                             userObject[0] = userId;
                             userObject[1] = userName;
                             userObject[2] = followIds;
                             userObject[3] = new Date();
                             list.add(userObject);
                             System.out.println("list:"+list.size());
                             // 处理仓库---TODO
                       }

                       System.out.println(Thread.currentThread().getName()+"::开始写库/Cache");
                       // 控制并发 BlockQueue
//                       persistService.batchInsertUserByArgs(list);
                       // 保证非空--
                       saveToDbResource.put(id,list);

                   }catch (Exception e){
                       //
                   }
               }
        }
    }

    private class CrawlUserRepoTask extends AbstractCrawlGithubATask{
        @Override
        public void run() {
            // star表示兴趣，fork代表参与，ISSUE参与，---
            // user → own repo → starred，fork user
            int index = 0;
            while (index<100){// 爬取前一百
                JSONArray array = requestUserInfo(index);
                //https://api.github.com/users/defunkt/repos
                Iterator<Object> iterator = array.iterator();
                // 遍历用户
                List<Object[]> insertIntoDbArgs = new LinkedList<>();
                while (iterator.hasNext()){
                    JSONObject jsonObject = new JSONObject(iterator.next());
                    String repoUrl = jsonObject.getStr("repos_url");
                    int userId = jsonObject.getInt("id");
                    JSONArray repoArray  = new JSONArray(request(repoUrl));
                    // 遍历该用户的仓库
                    Iterator<Object> repoIterator  = repoArray.iterator();
                    while (repoIterator.hasNext()){
                          JSONObject repoJsonObject = new JSONObject(repoIterator.next());

                          String repoFullName = repoJsonObject.getStr("full_name");
                          Object[] args = new Object[4];
                          args[0] = userId;
                          args[1] = repoFullName;
                          args[2] = "own";
                          args[3] = new Date();
                          insertIntoDbArgs.add(args);
                          String starUrl = repoJsonObject.getStr("stargazers_url");
                          String forkUrl = repoJsonObject.getStr("forks_url");
                          if(starUrl!=null){
                              JSONArray startJsonArr = new JSONArray(request(starUrl));
                              Iterator<Object> starRepoIterator = startJsonArr.iterator();
                              while (starRepoIterator.hasNext()){
                                  JSONObject startRepoJsonObject = new JSONObject(starRepoIterator.next());
                                  args = new Object[4];
                                  args[2] = "star";// 会不会影响前面的值？？
                                  args[0] = startRepoJsonObject.getInt("id");
                                  args[1] = repoFullName;
                                  args[3] = new Date();
                                  insertIntoDbArgs.add(args);
                              }
                          }
                    }
                    System.out.println("开始写库---");
                    persistService.batchInsertUserRepoByArgs(insertIntoDbArgs);
                    index = userId;
                }

            }
        }
    }
}
